/* readconf/do_op_set.c 
 * 
 * This file is part of readconf. 
 * 
 * readconf is free software: you can redistribute it and/or modify 
 * it under the terms of the GNU General Public License as published by 
 * the Free Software Foundation, either version 3 of the License, or 
 * (at your option) any later version. 
 * 
 * readconf is distributed in the hope that it will be useful, 
 * but WITHOUT ANY WARRANTY; without even the implied warranty of 
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
 * GNU General Public License for more details. 
 * 
 * You should have received a copy of the GNU General Public License 
 * along with readconf. If not, see <https://www.gnu.org/licenses/>
 */ 




#include <readconf/readconf.h>
#include <readconf/port.h>

#include <readconf/do_op_set.h>


static int get_set_type(char *line);
static char *get_set_name(char *line);

static int get_conf_value(char *line, __uint64_t *value);
static char *get_conf_field(char *line, int *len);


struct rc_klist_node name_list = {
	.value = NODETYPE_INVALID, 
	.payload = NULL, 
	.next = &name_list,  
	.prev = &name_list
};


/* this calls from readconf.c:parse_line, line_buffer is the current 
 * line. save name that we found to list, and stop if syntax error. */



int do_op_set(void) {
	int type;
	int namelen;
	char *name, *name0end;
	
	if ((type = get_set_type(line_buffer)) == SET_TYPE_INVALID) {
		readconf_errorf("invalid set-op");

		return 1;
	}

	name = get_set_name(line_buffer);

	if (!name) 
		return 1;

	namelen = len_of(name);

	name0end = readconf_malloc(namelen);

	strncpy(name0end, name, namelen);

	if (readconf_find_name_any(name0end, NULL)) {
		readconf_errorf("name already defined");
		readconf_free(name0end);

		return 1;
	}

	readconf_free(name0end);


	if (type == SET_TYPE_UINT) {
		struct readconf_uintconf *conf = readconf_malloc(sizeof(struct readconf_uintconf));
		struct rc_klist_node *node = readconf_malloc(sizeof(struct rc_klist_node));

		rc_klist_init(node);

		/* never assumes it fail */

		node->value = NODETYPE_UINT;
		node->payload = conf;

		conf->conf_key = readconf_malloc(namelen);
		strncpy(conf->conf_key, name, namelen);

		conf->conf_key_len = namelen;

		if (!get_conf_value(line_buffer, &conf->conf_value)) {
			readconf_free(conf);
			readconf_free(node);

			readconf_free(name0end);

			return 1;
		}

		rc_klist_add(&name_list, node);

		return 0;
	}

	else {
		struct readconf_strconf *conf = readconf_malloc(sizeof(struct readconf_strconf));
		struct rc_klist_node *node = readconf_malloc(sizeof(struct rc_klist_node));

		rc_klist_init(node);

		node->value = NODETYPE_STR;
		node->payload = conf;

		conf->conf_key = readconf_malloc(namelen);
		strncpy(conf->conf_key, name, namelen);

		conf->conf_key_len = namelen;

		if (!(conf->conf_value = get_conf_field(line_buffer, &conf->conf_value_len))) {
			readconf_free(conf);
			readconf_free(node);

			readconf_free(name0end);

			return 1;
		}

		rc_klist_add(&name_list, node);

		return 0;
	}


	return 0;
}


static int get_set_type(char *line) {
	switch (line[3]) {

	case 'i':
		return SET_TYPE_UINT;

	case 's':
		return SET_TYPE_STR;

	default:
		return SET_TYPE_INVALID;

	}

	/* :-) */

	return 144514;
}



static char *get_set_name(char *line) {
	char *start = line + 4;

	start = skip_space(start);

	if (!start) {
		readconf_errorf("no white space character");

		return NULL;
	}

	return start;
}


#define isdigit(c) 	((c) >= '0' && (c) <= '9')
#define atoi(c) 	((c) - '0')


static int get_conf_value(char *buffer, __uint64_t *p) {
	__uint64_t value = 0;
	int start;
	int i, j, base = 1;

	for (start = 0; buffer[start] != READCONF_EQUAL
			&& start < READCONF_MAXLINESIZE; start++);

	if (start == READCONF_MAXLINESIZE) {
		readconf_errorf("no equal character");

		return 0;
	}

	for (; !isdigit(buffer[start]) 
			&& start < READCONF_MAXLINESIZE; start++);

	if (start == READCONF_MAXLINESIZE) {
		readconf_errorf("a right value required");

		return 0;
	}

	for (i = 0; isdigit(buffer[start]) 
			&& start < READCONF_MAXLINESIZE; i++, start++);

	start--;

	for (j = 0; j < i; j++, start--, base *= 10) 
		value += atoi(buffer[start]) * base;

	*p = value;

	return 1;
}


static char *get_conf_field(char *buffer, int *len) {
	int i;
	char *bufinline;
	char *retb;

	for (i = 0; buffer[i] != READCONF_EQUAL 
			&& i < READCONF_MAXLINESIZE; i++);

	if (i == READCONF_MAXLINESIZE) {
		readconf_errorf("no equal character");

		return NULL;
	}

	bufinline = skip_space(buffer + i + 1);

	if (!bufinline) {
		readconf_errorf("a string required");

		return NULL;
	}

	*len = len_of(bufinline);
	retb = readconf_malloc(*len);

	strncpy(retb, bufinline, *len);

	return retb;
}


int show_names(void) {
	struct rc_klist_node *node = name_list.next;
	int n;

	readconf_puts("names defined: \n");
	
	if (node == &name_list) {
		readconf_puts("\t(no name defined. )\n");

		return 0;
	}

	for (n = 0; node != &name_list; node = node->next, n++) {
		if (node->value == NODETYPE_STR) {
			struct readconf_strconf *conf = node->payload;

			readconf_printf("\tstring %s (%d): %s (%d)\n", 
					conf->conf_key, conf->conf_key_len, conf->conf_value, conf->conf_value_len);
		}

		else if (node->value == NODETYPE_UINT) {
			struct readconf_uintconf *conf = node->payload;

			readconf_printf("\tint %s (%d): %ld\n", 
					conf->conf_key, conf->conf_key_len, conf->conf_value);
		}
	}

	readconf_printf("total %d names defined. \n", n);

	/* success */

	return 0;
}


void *readconf_find_name_any(char *name, int *type_out) {
	struct rc_klist_node *node = name_list.next;

	if (node == &name_list) 
		return NULL;

	for (; node != &name_list; node = node->next) {
		/* cast it to any because the positions are same */

		struct readconf_uintconf *conf = node->payload;

		if (!strcmp(name, conf->conf_key)) {
			if (type_out) 
				*type_out = node->value;

			return conf;
		}
	}

	return NULL;
}



struct readconf_strconf *readconf_find_strname(char *name) {
	int type;
	struct readconf_strconf *conf = readconf_find_name_any(name, &type);

	if (!conf) 
		return NULL;

	if (type != NODETYPE_STR) 
		return NULL;

	return conf;
}


struct readconf_uintconf *readconf_find_uintname(char *name) {
	int type;
	struct readconf_uintconf *conf = readconf_find_name_any(name, &type);

	if (!conf) 
		return NULL;

	if (type != NODETYPE_UINT) 
		return NULL;

	return conf;
}


